#ifndef NANE_SCRIPTSERIALIZER_H
#   define NANE_SCRIPTSERIALIZER_H

#   include "nane/ScriptSystem/IScriptSystem.h"
#   include "nane/core/IFileSystem.h"
#   include "nane/core/MemoryStream.h"
#   include "nane/core/IO.h"
#   include "nane/core/log.h"
#   include "nane/core/typetraits.h"

namespace nane
{

    static const char* cScriptValueTypeToString[EScriptValueType::size] = { "Unknown"
                                                                          , "Integer"
                                                                          , "Boolean"
                                                                          , "Float"
                                                                          , "String"
                                                                          , "Object" };
    
    // assume integer type
    template<typename T>
    struct ScriptWrapT
    {
        static const EScriptValueType Type()
        {
            return EScriptValueType::Integer;
        }
        
        inline static bool GetValue( TScriptValue& _scriptValue, EScriptValueType _type, T* _value )
        { 
            if( _type == EScriptValueType::Integer )
            {
                *_value = static_cast<T>(_scriptValue.i);
            }
            else if( _type == EScriptValueType::Float )
            {
                *_value = static_cast<T>(static_cast<int>(_scriptValue.f)); 
            }
            else
            {
                return false;
            }
            return true; 
        }
        inline static void SetValue( TScriptValue& _scriptValue, EScriptValueType _type, T _value )
        { 
            if( _type == EScriptValueType::Integer )
            {
                _scriptValue.i = static_cast<int>(_value);
            }
            else if( _type == EScriptValueType::Float )
            {
                _scriptValue.f = static_cast<float>(static_cast<int>(_value));
            }
            else
            {
                // no acceptable conversion
            }
        }
    };

    template<>
    struct ScriptWrapT<bool>
    {
        static const EScriptValueType Type()
        {
            return EScriptValueType::Boolean;
        }
        
        inline static bool GetValue( TScriptValue& _scriptValue, EScriptValueType _type, bool* _value )
        { 
            if( _type == EScriptValueType::Boolean )
            {
                *_value = _scriptValue.b;
            }
            else
            {
                return false;
            }
            return true;
        }
        inline static void SetValue( TScriptValue& _scriptValue, EScriptValueType _type, bool _value )
        { 
            if( _type == EScriptValueType::Boolean )
            {
                _scriptValue.b = _value;
            }
            else
            {
                // error
            }
        }
    };

    template<>
    struct ScriptWrapT<float>
    {
        static const EScriptValueType Type()
        {
            return EScriptValueType::Float;
        }
        
        inline static bool GetValue( TScriptValue& _scriptValue, EScriptValueType _type, float* _value )
        { 
            if( _type == EScriptValueType::Integer )
            {
                *_value = static_cast<float>(_scriptValue.i);
            }
            else if( _type == EScriptValueType::Float )
            {
                *_value = _scriptValue.f;
            }
            else
            {
                return false;
            }
            return true;
        }
        inline static void SetValue( TScriptValue& _scriptValue, EScriptValueType _type, float _value )
        { 
            if( _type == EScriptValueType::Integer )
            {
                _scriptValue.i = static_cast<int>(_value);
            }
            else if( _type == EScriptValueType::Float )
            {
                _scriptValue.f = _value;
            }
        }
    };

    template<>
    struct ScriptWrapT<const char*>
    {
        static const EScriptValueType Type()
        {
            return EScriptValueType::String;
        }
        
        inline static bool GetValue( TScriptValue& _scriptValue, EScriptValueType _type, const char** _value )
        { 
            if( _type == EScriptValueType::String )
            {
                *_value = _scriptValue.s;
            }
            else
            {
                return false;
            }
            return true; 
        }
        inline static void SetValue( TScriptValue& _scriptValue, EScriptValueType _type, const char* _value )
        { 
            if( _type == EScriptValueType::String )
            {
                _scriptValue.s = _value;
            }
            else
            {
                // error
            }
        }
    };

    template<>
    struct ScriptWrapT<IScriptObject*>
    {
        static const EScriptValueType Type()
        {
            return EScriptValueType::Object;
        }
        
        inline static bool GetValue( TScriptValue& _scriptValue, EScriptValueType _type, IScriptObject** _value )
        { 
            if( _type == EScriptValueType::Object )
            {
                *_value = _scriptValue.o;
            }
            else
            {
                return false;
            }
            return true;
        }
        inline static void SetValue( TScriptValue& _scriptValue, EScriptValueType _type, IScriptObject* _value )
        { 
            if( _type == EScriptValueType::Object )
            {
                _scriptValue.o = _value;
            }
            else
            {
                // error
            }
        }
    };
   
    
    struct ScriptSerializer
    {
        static bool Deserialize( IInput* _input, EScriptValueType* _argType, TScriptValue* _value );
        static bool Serialize( IOutput* _output, EScriptValueType _argType, TScriptValue _value );
    };

    template<typename T>
    struct ScriptSerializerType
    {
        static bool Deserialize( IInput* _input, T* _arg )
        {
            EScriptValueType valueType = EScriptValueType::Unknown;
            TScriptValue scriptValue;
            if( ScriptSerializer::Deserialize(_input, &valueType, &scriptValue) == false )
            {
                LOG_ERROR("Failed to deserialize script value");
                return false;
            }
            
            if( ScriptWrapT<T>::GetValue(scriptValue, valueType, _arg) == false )
            {
                LOG_ERROR("Failed to convert '%1' script value to '%2' type") 
                        << cScriptValueTypeToString[valueType.raw_value()] 
                        << cScriptValueTypeToString[ScriptWrapT<T>::Type().raw_value()];
                return false;
            }
            return true;
        }
        
        static bool Serialize( IOutput* _output, T _arg )
        {
            EScriptValueType valueType = ScriptWrapT<T>::Type();
            TScriptValue scriptValue;
            ScriptWrapT<T>::SetValue(scriptValue, valueType, _arg);
            return ScriptSerializer::Serialize(_output, valueType, scriptValue);
        }
    };
    
    template<>
    struct ScriptSerializerType<NullType>
    {
        static bool Deserialize( IInput* _input, NullType* _arg )
        {
            return true;
        }
        
        static bool Serialize( IOutput* _output, NullType _arg )
        {
            return true;
        }
    };

    template<uint32 bufferSize>
    class ScriptParamsBufferBase
        : public IInput
        , public IOutput
    {
    public:
        ScriptParamsBufferBase()
            : m_stream(m_buffer, bufferSize)
        {

        }

    public:
        uint32 Read( byte* _bufer, uint32 _count )
        {
            return m_stream.Read(_bufer, _count);
        }
        
        uint32 Write( const byte* _data, uint32 _count )
        {
            return m_stream.Write(_data, _count);
        }
        
        void Seek( uint32 _pos )
        {
            m_stream.Seek(_pos);
        }

    protected:
        IOutput* GetStream()
        {
            return &m_stream;
        }
        
    private:
        byte m_buffer[bufferSize];
        
    protected:
        MemoryStream m_stream;
    };
    
    template<typename P1, typename P2, typename P3, typename P4>
    struct NonVoidOrNullCount
    {
        enum Domain
        { 
            value = (( IsVoidT<P1>::value || IsNullT<P1>::value ) ? 0 : 1)
                                + (( IsVoidT<P2>::value || IsNullT<P2>::value ) ? 0 : 1)
                                + (( IsVoidT<P3>::value || IsNullT<P3>::value ) ? 0 : 1)
                                + (( IsVoidT<P4>::value || IsNullT<P4>::value ) ? 0 : 1) 
        };
    };

    template<typename P1, typename P2, typename P3, typename P4>
    class ScriptParamsBuffer
        : public ScriptParamsBufferBase<sizeof(int) + NonVoidOrNullCount<P1, P2, P3, P4>::value * (sizeof(EScriptValueType) + sizeof(TScriptValue))>
    {
    public:
        static const uint32 bufferSize = sizeof(int) + NonVoidOrNullCount<P1, P2, P3, P4>::value * (sizeof(EScriptValueType) + sizeof(TScriptValue));
        ScriptParamsBuffer()
            : ScriptParamsBufferBase<sizeof(int) + NonVoidOrNullCount<P1, P2, P3, P4>::value * (sizeof(EScriptValueType) + sizeof(TScriptValue))>()
        {
        }
            
        ScriptParamsBuffer( P1 _p1, P2 _p2, P3 _p3, P4 _p4 )
            : ScriptParamsBufferBase<sizeof(int) + NonVoidOrNullCount<P1, P2, P3, P4>::value * (sizeof(EScriptValueType) + sizeof(TScriptValue))>()
        {
            IO<IOUnsafe>::Write<int>(ScriptParamsBufferBase<bufferSize>::GetStream(), NonVoidOrNullCount<P1, P2, P3, P4>::value);
            //ScriptSerializerType<T1, T2, T3, T4>::serialize(getStream(), _p1, _p2, _p3, _p4);
            ScriptSerializerType<P1>::Serialize(ScriptParamsBufferBase<bufferSize>::GetStream(), _p1);
            ScriptSerializerType<P2>::Serialize(ScriptParamsBufferBase<bufferSize>::GetStream(), _p2);
            ScriptSerializerType<P3>::Serialize(ScriptParamsBufferBase<bufferSize>::GetStream(), _p3);
            ScriptSerializerType<P4>::Serialize(ScriptParamsBufferBase<bufferSize>::GetStream(), _p4);
            ScriptParamsBufferBase<bufferSize>::m_stream.Seek(0);
        }
    };

}   // namespace nane

#endif	// NANE_SCRIPTSERIALIZER_H
